package com.xu.cloud.common.process;

import com.xu.cloud.common.api.XuApi;
import com.xu.cloud.common.api.XuApiMethod;
import com.xu.cloud.common.constant.HttpConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * XuApi代理对象注册到RequestMappingHandlerMapping
 *
 * @author xuguan
 * @since 2025/1/16
 */
@Slf4j
public class ProcessorHandlerMapping implements ApplicationContextAware, InitializingBean {
    private final ObjectProvider<RequestMappingHandlerMapping> handlerMapping;
    private ApplicationContext applicationContext;

    public ProcessorHandlerMapping(ObjectProvider<RequestMappingHandlerMapping> requestMappingHandlerMappingObjectProvider) {
        this.handlerMapping = requestMappingHandlerMappingObjectProvider;
    }

    protected boolean isHandler(@Nullable Class<?> beanType) {
        return Objects.nonNull(beanType)
                && AnnotatedElementUtils.hasAnnotation(beanType, XuApi.class);
    }

    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        XuApiMethod xuApiMethod = AnnotatedElementUtils.findMergedAnnotation(method, XuApiMethod.class);

        if (Objects.isNull(xuApiMethod)) {
            return null;
        }

        final String[] apiMethodPaths = xuApiMethod.path().length == 0
                ? new String[]{xuApiMethod.apiCode()}
                : xuApiMethod.path();

        final Set<String> result = new HashSet<>();
        final Set<String> methodPaths = Arrays.stream(apiMethodPaths).map(s -> s.startsWith("/") ? s : "/" + s).collect(Collectors.toSet());
        final XuApi xuApi = AnnotatedElementUtils.findMergedAnnotation(handlerType, XuApi.class);
        if (Objects.nonNull(xuApi) && xuApi.path().length > 0) {
            final Set<String> apiPaths = Arrays.stream(xuApi.path())
                    .map(s -> {
                        s = s.startsWith("/") ? s : "/" + s;
                        return s;
                    })
                    .collect(Collectors.toSet());
            for (String apiPath : apiPaths) {
                for (String methodPath : methodPaths) {
                    result.add(apiPath + methodPath);
                }
            }
        } else {
            result.addAll(methodPaths);
        }

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(StringUtils.toStringArray(result))
                .methods(xuApiMethod.method())
                .consumes(xuApiMethod.consumes())
                .produces(xuApiMethod.produces());
        return builder.build();


    }

    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String
                ? applicationContext.getType((String) handler)
                : handler.getClass());
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        } catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                final RequestMappingHandlerMapping requestMappingHandlerMapping = handlerMapping.getIfAvailable();
                if (Objects.nonNull(requestMappingHandlerMapping)) {
                    requestMappingHandlerMapping.registerMapping(mapping, handler, method);
                    if (log.isDebugEnabled()) {
                        log.debug("register handler method: " + method + ", mapping: " + mapping);
                    }
                }
            });
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        final String[] beanNames = applicationContext.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            final Class<?> beanType = applicationContext.getType(beanName);
            if (isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
}
